/*-------------------------------------------------------------------------
 *
 * placeholder.c
 *	  PlaceHolderVar 和 PlaceHolderInfo 操作例程
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/util/placeholder.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "nodes/nodeFuncs.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "utils/lsyscache.h"

/* 本地函数 */
static void fc_find_placeholders_recurse(PlannerInfo *fc_root, Node *fc_jtnode);
static void fc_find_placeholders_in_expr(PlannerInfo *fc_root, Node *fc_expr);


/*
 * make_placeholder_expr
 *		为给定表达式创建一个 PlaceHolderVar。
 *
 * phrels 是要归属给表达式的语法位置（作为基关系的集合）。
 */
PlaceHolderVar *
make_placeholder_expr(PlannerInfo *fc_root, Expr *fc_expr, Relids fc_phrels)
{
	PlaceHolderVar *fc_phv = makeNode(PlaceHolderVar);

	fc_phv->phexpr = fc_expr;
	fc_phv->phrels = fc_phrels;
	fc_phv->phid = ++(fc_root->glob->lastPHId);
	fc_phv->phlevelsup = 0;

	return fc_phv;
}

/*
 * find_placeholder_info
 *		获取给定 PHV 的 PlaceHolderInfo
 *
 * 如果 PlaceHolderInfo 尚不存在，则如果 create_new_ph 为
 * true，创建它，否则抛出错误。
 *
 * 这与 make_placeholder_expr 是分开的，因为子查询提升必须
 * 为在上层查询中可能根本不使用的表达式创建 PlaceHolderVars，或者
 * 可能在常量表达式简化后不再存在。我们只对在传递给 query_planner() 的
 * 简化查询中仍然存在的 PHVs 构建 PlaceHolderInfos。
 *
 * 注意：这应该在 query_planner() 开始后调用。此外，
 * 在 deconstruct_jointree 开始后，create_new_ph 不能为 true，
 * 因为 make_outerjoininfo 假设我们已经知道所有占位符。
 */
PlaceHolderInfo *
find_placeholder_info(PlannerInfo *fc_root, PlaceHolderVar *fc_phv,
					  bool fc_create_new_ph)
{
	PlaceHolderInfo *fc_phinfo;
	Relids		fc_rels_used;
	ListCell   *fc_lc;

	/* 如果这一点不再成立，我们需要能够查看父列表 */
	Assert(fc_phv->phlevelsup == 0);

	foreach(fc_lc, fc_root->placeholder_list)
	{
		fc_phinfo = (PlaceHolderInfo *) lfirst(fc_lc);
		if (fc_phinfo->phid == fc_phv->phid)
			return fc_phinfo;
	}

	/* 未找到，因此创建它 */
	if (!fc_create_new_ph)
		elog(ERROR, "too late to create a new PlaceHolderInfo");

	fc_phinfo = makeNode(PlaceHolderInfo);

	fc_phinfo->phid = fc_phv->phid;
	fc_phinfo->ph_var = copyObject(fc_phv);

	/*
	 * 任何在 PHV 语法范围之外的引用关系都是
	 * LATERAL 引用，这些应该包含在 ph_lateral 中但不在
	 * ph_eval_at 中。如果没有引用的关系在语法范围内，
	 * 强制在语法位置进行评估。
	 */
	fc_rels_used = pull_varnos(fc_root, (Node *) fc_phv->phexpr);
	fc_phinfo->ph_lateral = bms_difference(fc_rels_used, fc_phv->phrels);
	if (bms_is_empty(fc_phinfo->ph_lateral))
		fc_phinfo->ph_lateral = NULL;	/* 如果为空，则确切设置为 NULL */
	fc_phinfo->ph_eval_at = bms_int_members(fc_rels_used, fc_phv->phrels);
	/* 如果没有包含变量，强制在语法位置进行评估 */
	if (bms_is_empty(fc_phinfo->ph_eval_at))
	{
		fc_phinfo->ph_eval_at = bms_copy(fc_phv->phrels);
		Assert(!bms_is_empty(fc_phinfo->ph_eval_at));
	}
	/* ph_eval_at 可能会稍后更改，请参见 update_placeholder_eval_levels */
	fc_phinfo->ph_needed = NULL;	/* 最初未使用 */
	/* 目前，使用数据类型信息估算宽度 */
	fc_phinfo->ph_width = get_typavgwidth(exprType((Node *) fc_phv->phexpr),
									   exprTypmod((Node *) fc_phv->phexpr));

	fc_root->placeholder_list = lappend(fc_root->placeholder_list, fc_phinfo);

	/*
	 * PHV 的包含表达式可能包含其他较低级别的 PHVs。我们
	 * 现在知道我们需要将它们也放入 PlaceHolderInfo 列表中，
	 * 所以我们可以立即做到这一点。
	 */
	fc_find_placeholders_in_expr(fc_root, (Node *) fc_phinfo->ph_var->phexpr);

	return fc_phinfo;
}

/*
 * find_placeholders_in_jointree
 *		在联接树中搜索 PlaceHolderVars，并构建 PlaceHolderInfos
 *
 * 我们不需要查看目标列表，因为 build_base_rel_tlists()
 * 会为目标列表中的任何 PHVs 创建条目。
 *
 * 这在我们开始 deconstruct_jointree 之前调用。一旦我们开始
 * deconstruct_jointree，所有活动占位符必须存在于
 * root->placeholder_list 中，因为 make_outerjoininfo 和
 * update_placeholder_eval_levels 需要在我们向上遍历连接树
 * 时获取此信息。
 */
void find_placeholders_in_jointree(PlannerInfo *fc_root)
{
	/* 如果查询不包含 PlaceHolderVars，我们无需做任何事情 */
	if (fc_root->glob->lastPHId != 0)
	{
		/* 在连接树顶部开始递归 */
		Assert(fc_root->parse->jointree != NULL &&
			   IsA(fc_root->parse->jointree, FromExpr));
		fc_find_placeholders_recurse(fc_root, (Node *) fc_root->parse->jointree);
	}
}

/*
 * find_placeholders_recurse
 *	  find_placeholders_in_jointree 的一个递归层级。
 *
 * jtnode 是当前要检查的联接树节点。
 */
static void fc_find_placeholders_recurse(PlannerInfo *fc_root, Node *fc_jtnode)
{
	if (fc_jtnode == NULL)
		return;
	if (IsA(fc_jtnode, RangeTblRef))
	{
		/* 这里没有要处理的条件 */
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_l;

		/*
		 * 首先，递归处理子联接。
		 */
		foreach(fc_l, fc_f->fromlist)
		{
			fc_find_placeholders_recurse(fc_root, lfirst(fc_l));
		}

		/*
		 * 现在处理顶级 quals。
		 */
		fc_find_placeholders_in_expr(fc_root, fc_f->quals);
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;

		/*
		 * 首先，递归处理子联接。
		 */
		fc_find_placeholders_recurse(fc_root, fc_j->larg);
		fc_find_placeholders_recurse(fc_root, fc_j->rarg);

		/* 处理条件子句 */
		fc_find_placeholders_in_expr(fc_root, fc_j->quals);
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
}

/*
 * find_placeholders_in_expr
 *		在给定表达式中找到所有 PlaceHolderVars，并为它们创建
 *		PlaceHolderInfo 条目。
 */
static void fc_find_placeholders_in_expr(PlannerInfo *fc_root, Node *fc_expr)
{
	List	   *fc_vars;
	ListCell   *fc_vl;

	/*
	 * pull_var_clause 做的事情超过了我们在这里需要的，但它可以工作，并且
	 * 使用起来很方便。
	 */
	fc_vars = pull_var_clause(fc_expr,
						   PVC_RECURSE_AGGREGATES |
						   PVC_RECURSE_WINDOWFUNCS |
						   PVC_INCLUDE_PLACEHOLDERS);
	foreach(fc_vl, fc_vars)
	{
		PlaceHolderVar *fc_phv = (PlaceHolderVar *) lfirst(fc_vl);

		/* 忽略任何普通变量 */
		if (!IsA(fc_phv, PlaceHolderVar))
			continue;

		/* 如果还没有，创建一个 PlaceHolderInfo 条目 */
		(void) find_placeholder_info(fc_root, fc_phv, true);
	}
	list_free(fc_vars);
}

/*
 * update_placeholder_eval_levels
 *		调整占位符的目标评估级别
 *
 * find_placeholder_info 设置的初始 eval_at 级别是使用
 * 在占位符表达式中的关系（或用于占位符语法位置下
 * 的整个子选择，如果表达式是无变量的）。如果查询
 * 包含任何可以使那些关系为空的外连接，我们必须将评估延迟到
 * 这些连接之上。
 *
 * 每当我们向 root->join_info_list 添加另一个外连接时，我们
 * 会重复此操作。这有点麻烦，但由于我们对占位符位置没有很多信息，
 * 很难避免。每个占位符的 eval_at 级别必须在其开始
 * 参与外连接延迟决策之前是正确的。
 *
 * 将来我们可能希望在这里放置额外的策略/启发式来
 * 尝试确定最佳评估级别。当前规则将导致在最低可能级别进行评估。
 * 然而，将占位符评估向上推到树上可能会进一步限制外连接的评估顺序，
 * 因此可能会适得其反；而且此时我们没有足够的信息来做出明智的选择。
 */
void update_placeholder_eval_levels(PlannerInfo *fc_root, SpecialJoinInfo *fc_new_sjinfo)
{
	ListCell   *fc_lc1;

	foreach(fc_lc1, fc_root->placeholder_list)
	{
		PlaceHolderInfo *fc_phinfo = (PlaceHolderInfo *) lfirst(fc_lc1);
		Relids		fc_syn_level = fc_phinfo->ph_var->phrels;
		Relids		fc_eval_at;
		bool		fc_found_some;
		ListCell   *fc_lc2;

		
/*
		 * 我们不需要对这个占位符做任何工作，除非新添加的外连接在其位置的语法上是低于的。
		 */
		if (!bms_is_subset(fc_new_sjinfo->syn_lefthand, fc_syn_level) ||
			!bms_is_subset(fc_new_sjinfo->syn_righthand, fc_syn_level))
			continue;

		/*
		 * 检查由于低外连接导致的延迟。这与initsplan.c中的check_outerjoin_delay逻辑相同，除了我们不
		 * 需要处理delay_upper_joins标志；高外连接的延迟将在稍后处理，基于我们现在计算的eval_at
		 * 值。
		 */
		fc_eval_at = fc_phinfo->ph_eval_at;

		do
		{
			fc_found_some = false;
			foreach(fc_lc2, fc_root->join_info_list)
			{
				SpecialJoinInfo *fc_sjinfo = (SpecialJoinInfo *) lfirst(fc_lc2);

				/* 忽略不在PHV的子选择中的连接 */
				if (!bms_is_subset(fc_sjinfo->syn_lefthand, fc_syn_level) ||
					!bms_is_subset(fc_sjinfo->syn_righthand, fc_syn_level))
					continue;

				/* 我们是否引用了这个 OJ 的任何可空 rel？ */
				if (bms_overlap(fc_eval_at, fc_sjinfo->min_righthand) ||
					(fc_sjinfo->jointype == JOIN_FULL &&
					 bms_overlap(fc_eval_at, fc_sjinfo->min_lefthand)))
				{
					/* 是的，我们是否已经在 eval_at 中包含了它的所有关系? */
					if (!bms_is_subset(fc_sjinfo->min_lefthand, fc_eval_at) ||
						!bms_is_subset(fc_sjinfo->min_righthand, fc_eval_at))
					{
						/* 没有，所以添加它们 */
						fc_eval_at = bms_add_members(fc_eval_at,
												  fc_sjinfo->min_lefthand);
						fc_eval_at = bms_add_members(fc_eval_at,
												  fc_sjinfo->min_righthand);
						/* 我们需要再进行一次迭代 */
						fc_found_some = true;
					}
				}
			}
		} while (fc_found_some);

		/* 不能将 PHV 的 eval_at 级别移动到其语法级别之上 */
		Assert(bms_is_subset(fc_eval_at, fc_syn_level));

		fc_phinfo->ph_eval_at = fc_eval_at;
	}
}

/*
 * fix_placeholder_input_needed_levels
 *		调整占位符输入的“所需在”级别
 *
 * 在我们确定所有占位符的 eval_at 级别后调用此函数。我们需要确保
 * 评估每个占位符所需的所有变量和占位符在扫描或连接的级别上可用，
 * 评估将在该处进行。（看起来扫描级别的评估似乎没有意思，但事实并非
 * 如此：占位符表达式中的 LATERAL 引用需要导致所引用的变量或占位符
 * 在评估所需的扫描中被标记为需要。）注意，这个循环可能会对其他
 * PlaceHolderInfos 的 ph_needed 集合产生副作用；这没关系，因为我们在
 * 这里不检查 ph_needed，所以不需要担心顺序问题。
 */
void fix_placeholder_input_needed_levels(PlannerInfo *fc_root)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_root->placeholder_list)
	{
		PlaceHolderInfo *fc_phinfo = (PlaceHolderInfo *) lfirst(fc_lc);
		List	   *fc_vars = pull_var_clause((Node *) fc_phinfo->ph_var->phexpr,
										   PVC_RECURSE_AGGREGATES |
										   PVC_RECURSE_WINDOWFUNCS |
										   PVC_INCLUDE_PLACEHOLDERS);

		add_vars_to_targetlist(fc_root, fc_vars, fc_phinfo->ph_eval_at, false);
		list_free(fc_vars);
	}
}

/*
 * add_placeholders_to_base_rels
 *		将所需的 PlaceHolderVars 添加到基础关系的目标列表中。
 *
 * 如果任何占位符可以在基础关系中计算并且在其上方是必需的，
 * 请将其添加到该关系的目标列表中。这看起来可能与 fix_placeholder_input_needed_levels
 * 合并，但由于连接移除发生在其间，并且可能会改变 ph_eval_at 集合，因此
 * 必须将其分开。在 add_placeholders_to_joinrel 中有基本相同的逻辑，
 * 但我们不能在连接关系形成之前执行那部分。
 */
void add_placeholders_to_base_rels(PlannerInfo *fc_root)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_root->placeholder_list)
	{
		PlaceHolderInfo *fc_phinfo = (PlaceHolderInfo *) lfirst(fc_lc);
		Relids		fc_eval_at = fc_phinfo->ph_eval_at;
		int			fc_varno;

		if (bms_get_singleton_member(fc_eval_at, &fc_varno) &&
			bms_nonempty_difference(fc_phinfo->ph_needed, fc_eval_at))
		{
			RelOptInfo *fc_rel = find_base_rel(fc_root, fc_varno);

			fc_rel->reltarget->exprs = lappend(fc_rel->reltarget->exprs,
											copyObject(fc_phinfo->ph_var));
			/* reltarget 的成本和宽度字段稍后将更新 */
		}
	}
}

/*
 * add_placeholders_to_joinrel
 *		将所需的 PlaceHolderVars 添加到连接关系的目标列表中；
 *		如果它们包含横向引用，将这些引用添加到连接关系的 direct_lateral_relids 中。
 *
 * 如果（a）PHV可以在此连接级别或以下计算并且（b）PHV在此级别上方是必需的，
 * 则连接关系应发出 PlaceHolderVar。然而，条件（a）足以添加到 direct_lateral_relids，
 * 如下所述。
 */
void add_placeholders_to_joinrel(PlannerInfo *fc_root, RelOptInfo *fc_joinrel,
							RelOptInfo *fc_outer_rel, RelOptInfo *fc_inner_rel)
{
	Relids		fc_relids = fc_joinrel->relids;
	ListCell   *fc_lc;

	foreach(fc_lc, fc_root->placeholder_list)
	{
		PlaceHolderInfo *fc_phinfo = (PlaceHolderInfo *) lfirst(fc_lc);

		/* 在这里可计算吗? */
		if (bms_is_subset(fc_phinfo->ph_eval_at, fc_relids))
		{
			/* 在此连接关系上方仍然需要吗? */
			if (bms_nonempty_difference(fc_phinfo->ph_needed, fc_relids))
			{
				/* 是的，将其添加到输出中 */
				fc_joinrel->reltarget->exprs = lappend(fc_joinrel->reltarget->exprs,
													fc_phinfo->ph_var);
				fc_joinrel->reltarget->width += fc_phinfo->ph_width;

				/*
				 * 如果 PHV 可以在这里计算但不能在任一输入中
				 * 计算，则收费评估所包含的表达式的成本。这有点不合理
				 * 因为我们根据考虑的第一对可能的输入关系做出决定。
				 * 对于其他配对，可能在一个输入或另一个输入中计算 PHV，让
				 * 我们对某些连接路径的 PHV 成本进行双重收费。暂时保留这个
				 * 问题，但我们可能希望稍后通过重新计算每对输入的 reltarget
				 * 成本来改进它。
*/
				if (!bms_is_subset(fc_phinfo->ph_eval_at, fc_outer_rel->relids) &&
					!bms_is_subset(fc_phinfo->ph_eval_at, fc_inner_rel->relids))
				{
					QualCost	fc_cost;

					cost_qual_eval_node(&fc_cost, (Node *) fc_phinfo->ph_var->phexpr,
										fc_root);
					fc_joinrel->reltarget->cost.startup += fc_cost.startup;
					fc_joinrel->reltarget->cost.per_tuple += fc_cost.per_tuple;
				}
			}

			/*
			 * 同样调整连接关系的 direct_lateral_relids 以包括
			 * PHV 的源关系。即使我们实际上不会发出 PHV，
			 * 我们也必须这样做，否则 join_is_legal() 将
			 * 拒绝有效的连接排序。（原则上，也许我们可以去掉
			 * 连接关系的 lateral_relids 依赖，但这很复杂，
			 * 并且不发出 PHV 的情况太少，不值得做这项工作。）
			 *
			 * 原则上，仅在连接尚未包含 PHV 的源关系时执行此操作。
			 * 但我们的调用者 build_join_rel() 将通过从直
			 * 接的 lateral_relids 中删除连接自己的 relids 来清理
			 * 事情，因此我们在这里不需要考虑这一点。
 */
			fc_joinrel->direct_lateral_relids =
				bms_add_members(fc_joinrel->direct_lateral_relids,
								fc_phinfo->ph_lateral);
		}
	}
}
